package com.yuandian.ops.workbench.service.impl;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.yuandian.bpm.common.core.util.R;
import com.yuandian.ops.workbench.mapper.SystemTrendAnalysisMapper;
import com.yuandian.ops.workbench.po.AlarmListParams;
import com.yuandian.ops.workbench.service.ISystemTrendAnalysisService;
import com.yuandian.ops.workbench.vo.SystemTrendAnalysisVo;
import com.yuandian.utils.YDateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * <p>
 *  系统趋势分析 实现
 * <p>
 *
 * @author linshihe
 * @date 2021/12/17
 * @since 1.0.0
 */
@Service
public class SystemTrendAnalysisServiceImpl implements ISystemTrendAnalysisService {

    private static Logger logger = LoggerFactory.getLogger(SystemTrendAnalysisServiceImpl.class);


    @Autowired
    private SystemTrendAnalysisMapper systemTrendAnalysisMapper;

    /**
     * 获取指定系统下业务的增量比TOP10
     *
     * @param params
     * @return
     */
    @Override
    public R<Object> getIncrementRatioList(AlarmListParams params) {
		// 业务统计表(分钟统计)
        params.setTableName("ads_business_detail_stat_m");

        // 往后推 1 分钟
        params.setEndTime(YDateUtil.getAfterMinuteStr(params.getEndTime(), 1));

        List<SystemTrendAnalysisVo> list = systemTrendAnalysisMapper.getIncrementRatioList(params);
        if (list == null || list.size() == 0) { return R.ok(null); }

        // 计算系统的增量比
        double systemIncrementRatio = getSystemIncrementRatio(params);

        // 以分钟为粒度获取各个业务访问量的最大值和最小值
        getMaxVisitCountAndMinVisitCount(list, params);

        /**
         * 过滤 minValAndTime 和 maxValAndTime 为空的数据
         *      因为第一个版本是用前端框选的 startTime和 endTime进行查询数据,执行getMaxVisitCountAndMinVisitCount（）方法后
         *      是不会存在 minValAndTime 和 maxValAndTime 为空的数据,但会带来前端列表无数据的问题（原因查看文档）
         *
         *      为了解决前端列表无数据的特殊问题，此次修改（2022-01-22），改成了用系统维度最大值和最小值所在时间点形成的时间区间去计算业务的增量比，
         *      由于缩小了查询时间区间，所以需要过滤掉不在这个区间内的数据（过滤依据：minValAndTime 和 maxValAndTime 为空的数据）
         */
        list = list.stream().filter(a -> (a.getMinValAndTime()!= null && a.getMaxValAndTime() != null)).collect(Collectors.toList());


        // 计算业务的增量比
        for (SystemTrendAnalysisVo systemTrendAnalysisVo : list) {
            getIncrementRatio(systemTrendAnalysisVo, params);
        }

        // 针对 增量比 进行特殊排序
        if ("incrementRatio".equals(params.getOrderByColumn())) {
            list = sortListByIncrementRatio(list, params);
        }

        // 若 系统增量比 大于0则需要过滤掉 业务增量比 为负数的数据，反之则过滤掉业务增量比为正数的数据
        if (systemIncrementRatio >= 0) {
            list = list.stream().filter(a -> Double.parseDouble(a.getIncrementRatio()) > 0).collect(Collectors.toList());
        }else {
            list = list.stream().filter(a -> Double.parseDouble(a.getIncrementRatio()) < 0).collect(Collectors.toList());
        }

        return R.ok(list.size() > 10 ? list.subList(0, 10) : list);
    }

    /**
     * 针对 增量比 进行特殊排序
     * @param list
     */
    private List<SystemTrendAnalysisVo> sortListByIncrementRatio(List<SystemTrendAnalysisVo> list, AlarmListParams params) {
        List<SystemTrendAnalysisVo> resList = new ArrayList<>();
        String orderByColumn = params.getOrderByColumn();
        String isAsc = params.getIsAsc();

        if (StringUtils.isNotBlank(orderByColumn)) {
            Comparator<SystemTrendAnalysisVo> comparator = null;
            if ("desc".equalsIgnoreCase(isAsc)) {
                comparator = Comparator.comparing(SystemTrendAnalysisVo::getOrderByRatio).reversed();
            } else {
                comparator = Comparator.comparing(SystemTrendAnalysisVo::getOrderByRatio);
            }
            resList = list.stream().sorted(comparator).collect(Collectors.toList());

        }
        return resList;
    }

    /**
     * 计算系统的增量比
     * @param params
     * @return
     */
    private double getSystemIncrementRatio(AlarmListParams params) {
        // 保存系统维度访问量最大值和最小值所在的时间
        List<String> visitCountTimeList = new ArrayList<>();
        // 查询系统对应时间内的访问量
        List<SystemTrendAnalysisVo> systemVisitCountList = systemTrendAnalysisMapper.getSystemVisitTrand(params);
        // 根据时间进行补0
        List<String> timeList = YDateUtil.getTimeList(params.getStartTime(), params.getEndTime(), 1);
        // 删除最后一个点，因为查询条件用的是 ( < endTime)
        timeList.remove(timeList.size() - 1);
        Map<String, SystemTrendAnalysisVo> collect = systemVisitCountList.stream().collect(Collectors.toMap(SystemTrendAnalysisVo::getCycleTime, v -> v, (k1, k2) -> k1));
        List<SystemTrendAnalysisVo> systemList = new ArrayList<>();
        for (String s : timeList) {
            s = s.substring(0, s.length() - 2);
            SystemTrendAnalysisVo vo = new SystemTrendAnalysisVo();
            // 补0
            vo.setVisitCount(collect.containsKey(s) ? collect.get(s).getVisitCount() : 0);
            vo.setCycleTime(s);
            vo.setSystemId(params.getSystemId());
            vo.setSystemName(collect.containsKey(s) ? collect.get(s).getSystemName() : null);
            systemList.add(vo);
        }
        // 遍历获取最大值和最小值
        SystemTrendAnalysisVo vo = new SystemTrendAnalysisVo();
        if (systemList != null && systemList.size() > 0) {
            // 初始化最大访问量和最小访问量
            int maxVisitCount = systemList.get(0).getVisitCount();
            int minVisitCount = systemList.get(0).getVisitCount();
            vo.setMaxValAndTime(maxVisitCount + "@" + systemList.get(0).getCycleTime());
            vo.setMinValAndTime(minVisitCount + "@" + systemList.get(0).getCycleTime());

            for (SystemTrendAnalysisVo systemTrendAnalysisVo : systemList) {
                int visitCount = systemTrendAnalysisVo.getVisitCount();
                String cycleTime = systemTrendAnalysisVo.getCycleTime();
                // 比较最大值(用 >= 是因为相同最大值时取最后的时间)
                if (visitCount >= maxVisitCount) {
                    vo.setMaxValAndTime(visitCount + "@" + cycleTime);
                    maxVisitCount = visitCount;
                }
                // 比较最小值（用 <= 是因为相同最小值时取最后的时间）
                if (visitCount <= minVisitCount) {
                    vo.setMinValAndTime(visitCount + "@" + cycleTime);
                    minVisitCount = visitCount;
                }

                // 回填系统名称
                if (StringUtils.isEmpty(vo.getSystemName()) && params.getSystemId() != null) {
                    vo.setSystemName(systemTrendAnalysisVo.getSystemName());
                }
            }
            visitCountTimeList.add(vo.getMaxValAndTime().split("@")[1]);
            visitCountTimeList.add(vo.getMinValAndTime().split("@")[1]);

            visitCountTimeList.sort((a, b) -> a.compareTo(b.toString()));

        }

        // 计算系统的增量比
        getIncrementRatio(vo, params);

        /*
            为了避免业务增量比列表出现数据被全部过滤掉的问题，改成了记录系统维度的最大值和最小值对应的时间点，
            然后那这个时间段去计算业务的增量比，下面往后推一分钟是因为SQL对时间范围的查询逻辑是 左闭又开
         */
        if (visitCountTimeList != null && visitCountTimeList.size() > 0) {
            params.setStartTime(visitCountTimeList.get(0));
            params.setEndTime(YDateUtil.getAfterMinuteStr(visitCountTimeList.get(1), 1));
        }

        return Double.parseDouble(vo.getIncrementRatio());
    }

    /**
     * 对最小值进行修正,因为在查询时间范围内的谋一分钟,若该业务不存在访问量,
     *      则此处需要在这一分钟进行补0从而得到最小访问量值（因为数据库不存在访问量为0的数据）
     *
     * @param resList  // 结果集列表
     * @param params
     */
    private void getMaxVisitCountAndMinVisitCount(List<SystemTrendAnalysisVo> resList, AlarmListParams params) {

        // 获取完整的时间刻度列表
        List<String> timeList = YDateUtil.getTimeList(params.getStartTime(), params.getEndTime(), 1);
        // 删除最后一个点，因为查询条件用的是 (< endTime)
        timeList.remove(timeList.size() - 1);

        // 保存每个业务的最大和最小访问量及对应时间
        Map<String, SystemTrendAnalysisVo> visitCountMap = new HashMap<>();

        // 查询每一分钟指定系统下的业务访问量列表
        List<SystemTrendAnalysisVo> voList = systemTrendAnalysisMapper.getVisitVountBySystemId(params);
        if (voList == null) { return; }

        // 数据转换 map<String, List<SystemTrendAnalysisVo>>
        Map<String, List<SystemTrendAnalysisVo>> map = new ConcurrentHashMap<>();
        for (SystemTrendAnalysisVo vo : voList) {
            map.computeIfAbsent(vo.getBusinessCode(), k -> new ArrayList<>());
            map.get(vo.getBusinessCode()).add(vo);
        }

        // 对voList中缺失的时间进行回填访问量为 0 的操作
        Set<String> keySet = map.keySet();
        for (Iterator<String> iterator = keySet.iterator(); iterator.hasNext();) {
            String key = iterator.next();
            List<SystemTrendAnalysisVo> l = map.get(key);
            if (l != null && l.size() > 0) {
                Map<String, SystemTrendAnalysisVo> collect = l.stream().collect(Collectors.toMap(SystemTrendAnalysisVo::getCycleTime, v -> v, (k1, k2) -> k1));
                // 对缺失的时间进行补0
                for (String s : timeList) {
                    s = s.substring(0, s.length() - 2);
                    if (!collect.containsKey(s)) {
                        SystemTrendAnalysisVo systemTrendAnalysisVo = new SystemTrendAnalysisVo();
                        systemTrendAnalysisVo.setBusinessCode(key);
                        systemTrendAnalysisVo.setVisitCount(0);
                        systemTrendAnalysisVo.setCycleTime(s);
                        l.add(systemTrendAnalysisVo);
                    }
                }
                SystemTrendAnalysisVo systemTrendAnalysisVo = new SystemTrendAnalysisVo();

                // 根据访问量升序和时间降序进行排序,则第一条数据代表最晚的且访问量值最小
                l.sort(Comparator.comparing(SystemTrendAnalysisVo::getVisitCount, Comparator.naturalOrder())
                        .thenComparing(SystemTrendAnalysisVo::getCycleTime, Comparator.reverseOrder()));
                systemTrendAnalysisVo.setMinValAndTime(l.get(0).getVisitCount() + "@" + l.get(0).getCycleTime());

                // 根据访问量降序和时间降序进行排序,则第一条数据代表最晚的且访问量值最大
                l.sort(Comparator.comparing(SystemTrendAnalysisVo::getVisitCount, Comparator.reverseOrder())
                        .thenComparing(SystemTrendAnalysisVo::getCycleTime, Comparator.reverseOrder()));
                systemTrendAnalysisVo.setMaxValAndTime(l.get(0).getVisitCount() + "@" + l.get(0).getCycleTime());

                visitCountMap.put(key, systemTrendAnalysisVo);
            }
        }

        // 更新访问量到结果集resList中
        for (SystemTrendAnalysisVo vo : resList) {
            if (visitCountMap.containsKey(vo.getBusinessCode())) {
                vo.setMinValAndTime(visitCountMap.get(vo.getBusinessCode()).getMinValAndTime());
                vo.setMaxValAndTime(visitCountMap.get(vo.getBusinessCode()).getMaxValAndTime());
            }
        }

    }

    /**
     * 计算增量比（MAX  MIN）
     *      （时间靠后的访问量值 - 时间靠前的访问量值) / 时间靠前的访问量值
     *
     * @param systemTrendAnalysisVo
     */
    private void getIncrementRatio(SystemTrendAnalysisVo systemTrendAnalysisVo, AlarmListParams params) {
        long startTime = System.currentTimeMillis();
        if (StringUtils.isEmpty(systemTrendAnalysisVo.getMaxValAndTime())
                || (StringUtils.isEmpty(systemTrendAnalysisVo.getMinValAndTime()))) {
            return;
        }
        // 增量比值
        double incrementRatioVal = 0;
        // 被除数（时间靠后的访问量值 - 时间靠前的访问量值)
        double dividend = 0;
        // 除数(时间靠前的访问量值)
        double divisor = 0;

        List<String> maxList = Arrays.asList(systemTrendAnalysisVo.getMaxValAndTime().split("@"));
        List<String> minList = Arrays.asList(systemTrendAnalysisVo.getMinValAndTime().split("@"));

        // 判断哪个值对应的时间靠后, 哪个值对应的时间靠前
        if (YDateUtil.compare(maxList.get(1), minList.get(1)) > 0) {

            dividend = Integer.parseInt(maxList.get(0)) - Integer.parseInt(minList.get(0));
            divisor = Integer.parseInt(minList.get(0));

        }else if (YDateUtil.compare(maxList.get(1), minList.get(1)) < 0) {

            dividend = Integer.parseInt(minList.get(0)) - Integer.parseInt(maxList.get(0));
            divisor = Integer.parseInt(maxList.get(0));
        }
        // 求 增量比(除数不能为0)
        if (divisor != 0) {
            incrementRatioVal = Double.valueOf(String.format("%.2f", dividend / divisor * 100));
        }else {
            incrementRatioVal = Double.valueOf(String.format("%.2f", dividend / 1 * 100));
        }
        // 回填增量比（String类型）保留两位小数
        systemTrendAnalysisVo.setIncrementRatio(getDoubleVal(incrementRatioVal));
        // 回填增量比的 绝对值 用于排序
        systemTrendAnalysisVo.setOrderByRatio(incrementRatioVal < 0 ? -incrementRatioVal : incrementRatioVal);
        long endTime = System.currentTimeMillis();

        logger.info("[计算【{} ~ {}）时间段内, {} 的增量比,在{}处得到访问量最大值: {},在{}处得到访问量最小值: {},增量比值为: {}, 耗时:{}MS]",
                params.getStartTime(), params.getEndTime(),
                StringUtils.isNotEmpty(systemTrendAnalysisVo.getBusinessName()) ? ("业务:"+systemTrendAnalysisVo.getBusinessName()):("系统:" + systemTrendAnalysisVo.getSystemName()),
                        maxList.get(1), maxList.get(0), minList.get(1), minList.get(0), incrementRatioVal+"%", endTime - startTime );
    }

    /**
     * 保留两位小数
     * @param val
     * @return
     */
    private String getDoubleVal(double val) {
        DecimalFormat df = new DecimalFormat("#######0.00");
        return df.format(val);
    }

}
